#include "core/Core.h"
#include "core/difftest.h"
#include "macro.h"

#include <cstdio>
#include <dlfcn.h>
#include <ostream>

void (*ref_difftest_memcpy)(word_t addr, void *buf, size_t n,
                            bool direction) = NULL;
void (*ref_difftest_regcpy)(void *dut, bool direction) = NULL;
void (*ref_difftest_exec)(uint64_t n) = NULL;
void (*ref_difftest_raise_intr)(uint64_t NO) = NULL;

static inline bool check_different(const char *name, word_t pc, word_t ref, word_t dut) {
	if (ref != dut) {
		std::printf("[" ANSI_FMT("%s", ANSI_FG_BLUE) "]" " is different after executing instruction at pc = " FMT_WORD ", " 
                ANSI_FMT("right = " FMT_WORD, ANSI_FG_GREEN) ", " ANSI_FMT("wrong = " FMT_WORD, ANSI_FG_RED) 
                ", diff = " FMT_WORD "\n",
				name, pc, ref, dut, ref ^ dut);
		return false;
	}
	return true;
}

void Core::difftest_check(word_t pc, DifftestContext* ref, DifftestContext* dut) {
    bool success = true;
    // check PC
    success &= check_different("pc", pc, *ref->pc, *dut->pc);

    // check GPR
    for (int i = 0; i < 32; i++) {
        success &= check_different(GPR::name_table[i].c_str(), pc, ref->gpr[i], dut->gpr[i]);
    }

    // check CSR
    success &= check_different("mstatus", pc, ref->csr[MSTATUS], dut->csr[MSTATUS]);
    success &= check_different("mie", pc, ref->csr[MIE], dut->csr[MIE]);
    success &= check_different("mtvec", pc, ref->csr[MTVEC], dut->csr[MTVEC]);
    success &= check_different("mepc", pc, ref->csr[MEPC], dut->csr[MEPC]);
    success &= check_different("mcause", pc, ref->csr[MCAUSE], dut->csr[MCAUSE]);
    // success &= check_different("mip", pc, ref->csr[MIP], dut->csr[MIP]);

    if (!success) {
        IFDEF(CONFIG_VTRACE, this->vtrace->flush());
        std::printf("inst count: %ld\n", this->inst_count);
        std::printf("cycle count: %ld\n", this->cycle_count);
        std::printf("last pc: " FMT_WORD "\n", this->last_pc);
        std::printf("last inst: %#x\n", this->last_inst);
        IFDEF(CONFIG_ITRACE, this->itrace->print());
        this->dump_regs();
        std::fflush(stdout);
        Panic("Differential testing failed");
    }
}

#ifdef CONFIG_DIFFTEST

void Core::difftest_init(const char *ref_file, uint8_t *mem_base, size_t mem_size) {
    assert(ref_file != NULL);

    void *handle = handle = dlopen(ref_file, RTLD_LAZY);
    assert(handle);

    ref_difftest_memcpy = reinterpret_cast<void (*)(word_t, void *, size_t, bool)>(dlsym(handle, "difftest_memcpy"));
    assert(ref_difftest_memcpy);

    ref_difftest_regcpy = reinterpret_cast<void (*)(void *, bool)>(dlsym(handle, "difftest_regcpy"));
    assert(ref_difftest_regcpy);

    ref_difftest_exec = reinterpret_cast<void (*)(uint64_t)>(dlsym(handle, "difftest_exec"));
    assert(ref_difftest_exec);

    ref_difftest_raise_intr = reinterpret_cast<void (*)(uint64_t)>(dlsym(handle, "difftest_raise_intr"));
    assert(ref_difftest_raise_intr);

    void (*ref_difftest_init)() = reinterpret_cast<void (*)()>(dlsym(handle, "difftest_init"));
    assert(ref_difftest_init);

    Log("Differential testing: \33[1;32m%s\33[0m", "ON");
    Log("The result of every instruction will be compared with %s. "
        "This will help you a lot for debugging, but also significantly reduce "
        "the performance. "
        "If it is not necessary, you can turn it off in include/common.h.",
        ref_file);

    ref_difftest_init();
    ref_difftest_memcpy(this->pc, mem_base, mem_size, DIFFTEST_TO_REF);

    dut_ctx = new DifftestContext { &this->pc, this->gpr.get_ptr(), this->csr.get_ptr() };
    ref_ctx = new DifftestContext { };

    ref_difftest_regcpy(dut_ctx, DIFFTEST_TO_REF);
}

void Core::difftest_step(word_t pc) {
    if (is_skip_ref) {
        ref_difftest_regcpy(dut_ctx, DIFFTEST_TO_REF);
        is_skip_ref = false;
        return;
    }

    ref_difftest_exec(1);

    if (!intr_event_queue.empty()) {
        ref_difftest_raise_intr(intr_event_queue.front());
        intr_event_queue.pop_front();
    }

    ref_difftest_regcpy(ref_ctx, DIFFTEST_TO_DUT);

    difftest_check(pc, ref_ctx, dut_ctx);
}

void Core::difftest_raise_intr(uint64_t NO) {
    ref_difftest_raise_intr(NO);
}

void Core::difftest_skip_ref() {
    is_skip_ref = true;
}

#endif
